﻿using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Sustainalytics.Entities;
using Sustainalytics.Entities.ESGRatings;
using Sustainalytics.Entities.ScreeningTool;
using System;
using System.Collections.Generic;
using System.Linq;
using Sustainalytics.Entities.RiskRatings;
using MongoDB.Bson;
using Sustainalytics.Entities.ConnectionStrings;
using Sustainalytics.MongoDB.Extensions;
using System.Configuration;
using Sustainalytics.DataLayer.ConnectionStrings;

namespace Sustainalytics.DataLayer
{
    public partial class CompanyScreeningRepository : ICompanyScreeningRepository
    {
        Dictionary<string, List<RiskRatingCluster>> _riskRatingClusters;

        public Dictionary<string, List<RiskRatingCluster>> RiskRatingClusters
        {
            get
            {
                if (_riskRatingClusters == null)
                    _riskRatingClusters = GetRiskRatingClusters();
                return _riskRatingClusters;
            }
            set => _riskRatingClusters = value;
        }

        public void FilterCriteriaReorder()
        {
            var filtersColection = _clientMongoDb.GetCollection<Filter>(typeof(Filter).Name);
            var query = new List<FilterDefinition<Filter>>
            {
                Builders<Filter>.Filter.Eq(p => p.Type, FilterType.Standard),
                Builders<Filter>.Filter.Eq(p => p.Status, FilterStatus.Draft),
                Builders<Filter>.Filter.Eq(p => p.UnPublishDate, null)
            };

            var filters = filtersColection.Find(Builders<Filter>.Filter.And(query)).ToList();
            foreach (var filter in filters)
            {
                var coll = _clientMongoDb.GetCollection<FilterCriteria>(typeof(FilterCriteria).Name);
                var criterias = coll.Find(Builders<FilterCriteria>.Filter.Eq(x => x.FilterId, filter.Id))
                                    .SortBy(x => x.Order)
                                    .ToList();

                var idx = 0;
                foreach (var c in criterias)
                {
                    c.Order = ++idx;
                    coll.FindOneAndReplace(Builders<FilterCriteria>.Filter.Eq(x => x.Id, c.Id), c);
                }
            }
        }
        public void PublishStandardFilters()
        {
            // for published filters  we need to update  order fields to start from 1
            FilterCriteriaReorder();


            var filtersColection = _clientMongoDb.GetCollection<Filter>(typeof(Filter).Name);
            var query = new List<FilterDefinition<Filter>>
            {
                Builders<Filter>.Filter.Eq(p => p.Type, FilterType.Standard),
                Builders<Filter>.Filter.Eq(p => p.Status, FilterStatus.Draft),
                Builders<Filter>.Filter.Eq(p => p.UnPublishDate, null)
            };

            filtersColection.UpdateMany(Builders<Filter>.Filter.And(query), 
                Builders<Filter>.Update.Set(p => p.Status, FilterStatus.Active));
        }

        public List<FilterCriteria> GetFilterCriteriasByFilterId(Guid filterId)
        {
            var filterCriteria = _clientMongoDb.GetCollection<FilterCriteria>(typeof(FilterCriteria).Name);

            var criterias =
                filterCriteria.Find(Builders<FilterCriteria>.Filter.Eq(x => x.FilterId, filterId))
                    .Project<FilterCriteria>(Builders<FilterCriteria>.Projection
                        .Include(f => f.Name)
                        .Include(f => f.FilterId)
                        .Include(f => f.SelectedValueInformation)
                        .Include(f => f.CriteriaType)
                        .Include(f => f.Order)
                        .Include(f => f.CriteriaEntityField)
                        .Include(f => f.CriteriaLevel2Id)
                        .Include(f => f.TemplateCriteriaId)
                        .Include(f => f.ResearchProducts))
                    .ToList();

            return criterias;
        }

        public Filter GetFilterByFilterId(Guid filterId)
        {
            var filtersColection = _clientMongoDb.GetCollection<Filter>(typeof(Filter).Name);
            var filter = filtersColection.Find(Builders<Filter>.Filter.Eq(x=> x.Id, filterId)).SingleOrDefault();

            return filter;
        }

        public string[] GetFieldsForFilterCriteria(Guid filterId)
        {
            var criteriaType4 = _clientMongoDb.GetCollection<FilterCriteria>(typeof(FilterCriteria).Name)
                .Find(Builders<FilterCriteria>.Filter.And(
                    Builders<FilterCriteria>.Filter.Eq(x => x.FilterId, filterId),
                    Builders<FilterCriteria>.Filter.Eq(x => x.CriteriaType, CriteriaTypeEnum.CriteriaType4)))
                .ToList();

            var criteriaType4ExtraFields = new List<string>();
            if (criteriaType4.Any())
            {
                criteriaType4.ForEach(x =>
                {
                    var stringFields = x.CriteriaEntityField.Split(' ');
                    criteriaType4ExtraFields.Add($"{stringFields[0]} {stringFields[1]} Category of Involvement");
                });
            }

            var fields = _clientMongoDb.GetCollection<FilterCriteria>(typeof(FilterCriteria).Name)
                .Find(Builders<FilterCriteria>.Filter.Eq(x => x.FilterId, filterId))
                .Project<FilterCriteria>(Builders<FilterCriteria>.Projection.Include(p => p.CriteriaEntityField))
                .ToList()
                .Select(x => x.CriteriaEntityField)
                .Concat(new[] {
                    "_id",
                    "CompanyName",
                    "CompanyNameLowerCase",
                    "UniverseIds",
                    nameof(ScreeningData.ResearchStates),
                })
                .Concat(criteriaType4ExtraFields)
                .Distinct()
                .ToArray();

            return fields;
        }

        public CriteriaType16 LoadCriteriaType16(Guid criteriaTemplateId)
        {
            var coll = _clientMongoDb.GetCollection<CriteriaType16>(typeof(TemplateCriteria).Name);

            var criteria = coll.AsQueryable().SingleOrDefault(cu => cu.Id == criteriaTemplateId);
            if (criteria == null)
                throw new ArgumentException(string.Format("Cannot find the criteria with id: {0}", criteriaTemplateId));

            return criteria;
        }

        public ESGProfile GetDefaultProfile()
        {
            return _clientMongoDb.GetCollection<ESGProfile>(typeof(ESGProfile).Name)
                .Find(Builders<ESGProfile>.Filter.Eq(p => p.Name, "Default")).SingleOrDefault();
        }

        public string GetAccountNameForFtpReportByAccountId(Guid accountId)
        {
            return _clientMongoDb.GetCollection<Account>(typeof(Account).Name)
                .Find(Builders<Account>.Filter.Eq(x=> x.Id, accountId))
                .Project(p => p.Name)
                .FirstOrDefault();
        }

        public List<Filter> GetAllFilters()
        {
            return _clientMongoDb.GetCollection<Filter>(typeof(Filter).Name)
                .Find(_ => true)
                .Project<Filter>(Builders<Filter>.Projection
                    .Include(p => p.AccountId)
                    .Include(p => p.UniverseId)
                    .Include(p => p.UniverseName))
                .ToList();
        }

        public void UpdateFilter(Guid filterId, int newUniverseId)
        {
            _clientMongoDb.GetCollection<Filter>("FilterUpdated").UpdateOne(
                Builders<Filter>.Filter.Eq(x => x.Id, filterId),
                Builders<Filter>.Update.Set(x => x.UniverseId, newUniverseId));
        }

        public void DuplicateFiltersCollection()
        {
            if (_clientMongoDb.CollectionExists("FilterUpdated"))
            {
                _clientMongoDb.DropCollection("FilterUpdated");
            }

            var unUpdatedFilters = _clientMongoDb.GetCollection<Filter>(typeof(Filter).Name);
            var updatedFilters = _clientMongoDb.GetCollection<Filter>("FilterUpdated");

            updatedFilters.InsertMany(unUpdatedFilters.Find(_ => true).ToList());
        }

        public void CleanUpOldFiltersCollection()
        {
            _clientMongoDb.RenameCollection(typeof(Filter).Name, "Filter_" + DateTime.Now.ToString("yyyyMMddHHmmss"));
            _clientMongoDb.RenameCollection("FilterUpdated", typeof(Filter).Name);
        }

        public T LoadTemplateCriteria<T>(Guid id) where T : IEntity
        {
            var coll = _clientMongoDb.GetCollection<T>(typeof(TemplateCriteria).Name);

            var criteria = coll.AsQueryable().FirstOrDefault(cu => cu.Id == id);
            if (criteria == null)
                throw new ArgumentException(string.Format("Cannot find the criteria with id: {0}", id));

            return criteria;
        }

        public CriteriaTypeThree LoadCriteriaTypeThreeSingleCollection(string collectionName)
        {
            var coll = _clientMongoDb.GetCollection<CriteriaTypeThree>(typeof(TemplateCriteria).Name);

            var criteria = coll.AsQueryable().SingleOrDefault(cu => cu.CriteriaEntityField.Equals(collectionName));
            if (criteria == null)
                throw new ArgumentException(string.Format("Cannot find the criteria with the related collection name: {0}", collectionName));

            return criteria;
        }

        public List<CriteriaTypeFour> LoadCriteriaTypeFour(Guid piFilterCriteriaId)
        {
            return _clientMongoDb.GetCollection<CriteriaTypeFour>(typeof(FilterCriteria).Name)
                .AsQueryable()
                .Where(cr => cr.PIFilterCriteriaId == piFilterCriteriaId)
                .ToList();
        }

        public List<RiskRatingCluster> GetRiskRatingClusters_Overall()
        {
            return RiskRatingClusters[RiskRatingClusterType.Overall.ToString()];
        }

        public List<RiskRatingCluster> GetRiskRatingClusters_Mei()
        {
            return RiskRatingClusters[RiskRatingClusterType.MEI.ToString()];
        }

        public string GetRiskRatingAnswerCategoryText(TemplateCriteria criteria, int value)
        {
            return thresholdDictionary[value];
        }

        private Dictionary<int, string> thresholdDictionary = new Dictionary<int, string>()
        {
            { -1, "No Minimum threshold selected" },
            { 4, "Companies with Severe Risk are restricted" },
            { 3, "Companies with Severe and High Risk are restricted" },
            { 2, "Companies with Severe, High and Medium Risk are restricted" },
            { 1, "Companies with Severe, High, Medium and Low Risk are restricted" },
            { 0, "Companies with Severe, High, Medium, Low and Negligible Risk are restricted" }
        };

        private Dictionary<string, List<RiskRatingCluster>> GetRiskRatingClusters()
        {
            var clusters = MongoFactory.GetCollectionFromDatabase<RiskRatingCluster>(GetConnectionString(ConnectionStringType.RiskRatingsResearchDW))
            .Find(new BsonDocument())
            .ToList();
            var dictionary = clusters.GroupBy(c => c.Type).ToDictionary(kv => kv.Key.ToString(), kv => kv.ToList());
            return dictionary;
        }

        private static string GetConnectionString(ConnectionStringType connectionStringType)
        {
            var clientPlatformConnectionString = ConfigurationManager.ConnectionStrings["ClientPlatform"].ConnectionString;

            var connectionStringsManager = ConnectionStringsManager.GetInstance(ConnectionStringsRepository.GetInstance(clientPlatformConnectionString));

            return connectionStringsManager.GetConnectionString(connectionStringType);
        }
    }
}
